﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using MahApps.Metro;
using Red.Controls.Shell;
using Red.Launcher.Dialogs;
using Red.Launcher.Utilities;
using P4 = Red.Core.Perforce;
using Packages = Red.Launcher.ViewModels.Packages;
using Workspaces = Red.Launcher.ViewModels.Workspaces;

namespace Red.Launcher.Executor
{
    public static class Run
    {
        #region Private Methods

        private static P4.Mediator CreateMediator( Workspaces.Workspace workspace )
        {
            P4.Mediator mediator = new P4.Mediator( Settings.PerforceServer, workspace.Credentials.Name, workspace.PerforceName );

            return mediator;
        }

        private static bool GameErrorCheck( Status status, Packages.Package engine, Workspaces.Workspace workspace )
        {
            //Clear the previous run error (if there was one)
            status.Clear( Status.Source.Run );
            status.Clear( Status.Source.PerforceLogin );

            status.Check( engine != null, Status.Source.Engine, "No Active Engine" );
            status.Check( workspace != null, Status.Source.WorkspaceManager, "No Workspace selected" );

            return status.HasError;
        }

        private static bool ToolkitErrorCheck( Status status, Packages.Package toolkit, Packages.Package engine, Workspaces.Workspace workspace )
        {
            GameErrorCheck( status, engine, workspace );

            status.Check( toolkit != null, Status.Source.Toolkit, "No Active Toolkit" );

            return status.HasError;
        }

        private static void ErrorCheck( Status status, Config config, Packages.Package runPkg, Packages.Package enginePkg, Workspaces.Workspace workspace )
        {
            //Clear the previous run errors (if there was one)
            status.Clear( Status.Source.Run );
            status.Clear( Status.Source.PerforceLogin );

            status.Check( runPkg != null, Status.Source.Toolkit, config.Error.MissingRunPackage );
            status.Check( workspace != null, Status.Source.WorkspaceManager, "No Workspace selected" );

            if ( config.SpecifyEngineExe )
            {
                status.Check( enginePkg != null, Status.Source.Engine, "No Active Engine" );
            }
        }

        private static bool ParameterCheck( Status status, Config config, Workspaces.Workspace workspace )
        {
            foreach ( Parameter parameter in config.Parameters )
            {
                if ( !workspace.Parameters.ContainsKey( parameter.Source ) )
                {
                    //TODO: Give this it's own status integer
                    status[ Status.Source.Run ] = $"Parameter \"{parameter.Source}\" is not defined in \"{workspace.DisplayName}\"";

                    return false;
                }
            }

            return true;
        }

        private static async Task< bool > MediatorErrorCheck( Status status, P4.Mediator mediator, Workspaces.Workspace workspace )
        {
            string p4error = "Invalid Perforce Login";
            P4.Mediator.ErrorEventHandler errorHandler = ( string message ) => { p4error = message.Trim(); };

            mediator.Error += errorHandler;
            status.Check( await mediator.SignInCheckAndPrompt(), Status.Source.PerforceLogin, p4error );
            mediator.Error -= errorHandler;

            return status.HasError;
        }

        #endregion Private Methods

        #region Public Methods

        /// <summary>
        /// Attempt to start a process using the operating system file association
        /// </summary>
        public static void ShellExecuteFilePath( string path, FrameworkElement parent )
        {
            try
            {
                Process.Start( path );
            }
            catch ( Win32Exception e )
            {
                string title = "Error occurred";
                string message = $"There was a problem opening \"{path}\":\n\n{e.Message}";
                RedMessageBox.Show( parent, RedDialogType.Ok, message, title, RedMessageBox.Mode.Warning );
            }
        }

        public static async Task OpenFile( string file )
        {
            Interop.Editor connection = new Interop.Editor();
            if ( await connection.Connect() )
            {
                comm.OpenFile msg = new comm.OpenFile();
                msg.resourcePath = file;
                connection.Channel.Send( msg );
            }
        }

        public static async Task<Diagnostics.Process.Monitor> CheckExistingProcesses( Diagnostics.Process.Manager processManager, string exePath )
        {
            string processName = Path.GetFileNameWithoutExtension( exePath );
            Diagnostics.Process.Monitor packageMonitor = processManager[ processName ];

            await packageMonitor.Refresh();
            if ( packageMonitor.Primary.Count == 1 )
            {
                packageMonitor.Primary[ 0 ].BringToForeground();
            }
            else if ( packageMonitor.Count >= 1 )
            {
                string message = $"You must close the following applications before you can run a new instance of '{exePath}'";
                WaitForProcessShutdownDialog dialog = new WaitForProcessShutdownDialog() { Owner = App.Current.MainWindow, Message = message };
                dialog.Monitors.Add( packageMonitor );
                dialog.ShowDialog();
            }

            return packageMonitor;
        }

        public enum WorkingDirectoryConfig
        {
            Unset,
            Installpath,
            ExePath
        }

        public enum PathRoot
        {
            None,
            Workspace,
            Engine
        }

        public class Parameter
        {
            public string Source { get; set; }
            public string Target { get; set; }
            public PathRoot PathRoot { get; set; }
        }

        public class Error
        {
            public string MissingRunPackage { get; set; }
        }

        public class Config
        {
            public string ExeOverride { get; set; }
            public bool UseShellExecute { get; set; }
            public bool SpecifyPerforce { get; set; }
            public bool SpecifyEngineExe { get; set; }
            public WorkingDirectoryConfig WorkingDirectory { get; set; }
            public List<Parameter> Parameters { get; set; }
            public Error Error { get; set; }
        }

        private static string FormatParameter( Parameter parameter, P4.Mediator mediator, Workspaces.Workspace workspace, Packages.Package enginePkg )
        {
            string root;

            switch ( parameter.PathRoot )
            {
                case PathRoot.Workspace:
                    root = mediator.GetDepotRoot();
                    break;

                case PathRoot.Engine:
                    root = enginePkg.InstallPath;
                    break;

                default:
                    root = string.Empty;
                    break;
            }

            string path = Path.Combine( root, workspace.Parameters[ parameter.Source ] );
            return $"-{parameter.Target}=\"{path}\"";
        }

        public static async Task<bool> Exe( Config config, Diagnostics.Process.Manager processManager, Status status, Packages.Package runPkg, Packages.Package enginePkg, Workspaces.Workspace workspace, string fileToOpen = null )
        {
            ErrorCheck( status, config, runPkg, enginePkg, workspace );
            ParameterCheck( status, config, workspace );

            if ( status.HasError )
                return false;

            P4.Mediator mediator = CreateMediator( workspace );
            if ( await MediatorErrorCheck( status, mediator, workspace ) )
                return false;

            string runPackageExe = ( config.ExeOverride == null ) ? runPkg.ExecutablePath : Path.Combine( runPkg.InstallPath, config.ExeOverride );
            Diagnostics.Process.Monitor monitor = await CheckExistingProcesses( processManager, runPackageExe );

            if ( monitor.Primary.Count == 1 && fileToOpen != null )
            {
                await OpenFile( fileToOpen );
            }
            else if( monitor.Primary.Count == 0 )
            {
                ProcessStartInfo info = new ProcessStartInfo( runPackageExe );

                info.UseShellExecute = config.UseShellExecute;

                switch( config.WorkingDirectory )
                {
                    case WorkingDirectoryConfig.Installpath:
                        info.WorkingDirectory = runPkg.InstallPath;
                        break;

                    case WorkingDirectoryConfig.ExePath:
                        info.WorkingDirectory = Path.GetDirectoryName( runPackageExe );
                        break;
                }

                string perforce = config.SpecifyPerforce ? $"-p4host={Settings.PerforceServer} -p4user={workspace.Credentials.Name} -p4workspace={workspace.PerforceName}" : string.Empty;
                string engineExe = config.SpecifyEngineExe ? $"-engineAppExePath=\"{enginePkg.ExecutablePath}\"" : string.Empty;
                string parameters = string.Join( " ", from parameter in config.Parameters select FormatParameter( parameter, mediator, workspace, enginePkg ) );

                info.Arguments = $"{perforce} {engineExe} {parameters}";

                try
                {
                    Process.Start( info );
                }
                catch ( Exception e )
                {
                    Debug.Print( e.ToString() );
                    status[ Status.Source.Run ] = e.Message;
                }
            }

            return status.HasError;
        }

        public static async Task< bool > Toolkit( Diagnostics.Process.Manager processManager, Status status, Packages.Package toolkit, Packages.Package engine, Workspaces.Workspace workspace )
        {
            if ( ToolkitErrorCheck( status, toolkit, engine, workspace ) )
            {
                return false;
            }

            P4.Mediator mediator = CreateMediator( workspace );
            if ( await MediatorErrorCheck( status, mediator, workspace ) )
            {
                return false;
            }

            string exePath = toolkit.ExecutablePath;
            Diagnostics.Process.Monitor monitor = await CheckExistingProcesses( processManager, exePath );

            if ( monitor.Count == 0 )
            {
                ProcessStartInfo info = new ProcessStartInfo( exePath );

                // Just run the executable, don't ask windows what it's associated with
                info.UseShellExecute = false;

                info.WorkingDirectory = toolkit.InstallPath;

                var theme = ThemeManager.DetectAppStyle();

                const string argumentsTemplate = "-editor=Red.Toolkit.AssetBrowser -p4server={0} -p4user={1} -p4workspace={2} {3} -engineAppExePath=\"{4}\" -Theme=\"{5}\" -Theme-Accent=\"{6}\"";

                info.Arguments = string.Format
                (
                    argumentsTemplate,
                    Settings.PerforceServer,
                    workspace.Credentials.Name,
                    workspace.PerforceName,
                    string.Join( " ", workspace.Parameters.Select( param => string.Format( "-{0}=\"{1}\"", param.Key, param.Value ) ) ),
                    engine.ExecutablePath,
                    theme.Item1.Name,
                    theme.Item2.Name
                );

                try
                {
                    Process.Start( info );
                }
                catch ( Exception e )
                {
                    Debug.Print( e.ToString() );
                    status[ Status.Source.Run ] = e.Message;
                }
            }

            return status.HasError;
        }

        #endregion Public Methods
    }
}
